"""
Transaction Manager module.

This module handle transactions, how to commit and rollback data to data base.

Last Modified: $Id: transactionmanager.py 28 2005-05-26 00:01:44Z gustavo $

@author: U{Gustavo Sverzut Barbieri <barbieri@gmail.com>}
@license: GNU GPL
@copyright: (C) 2005 Gustavo Sverzut Barbieri
@version: $Rev: 28 $
"""









import rawdb


class TransactionManagerInexistent( Exception ):
    """Transaction Manager Inexistent Transaction Exception.

    This exception is raised if a given transaction id was not existent.
    """
    def __init__( self, exception, tid ):
	Exception.__init__( self )
	self.tid = tid
	self.exception = exception
    # __init__()

    def __str__( self ):
	return "%s: given tid '%s' is not an existent transaction." % \
	       ( self.__class__.__name__, self.tid )
    # __str__()
# TransactionManagerInexistent


class TransactionManager( object ):
    """
    Handle transactions.

    Transactions have one Transaction ID and a working copy (shallow copy) of
    the Data Base. If one transaction is aborted, it's working copy is
    trowed away, if it's committed, the data base is changed to reflect the
    working copy.


    Creating a Transaction Manager (you must use the singleton!):

    >>> tm = singleton()


    Creating a Transaction:

    >>> t1 = tm.begin()
    >>> t2 = tm.begin()


    You can get the transaction's working copy:

    >>> db_t1 = tm.get_shallow_copy( t1 )
    >>> db_t2 = tm.get_shallow_copy( t2 )
    >>> db_t1[ 1 ] = 10 # to the working copy

    >>> db_t2[ 1 ]      # will not have the value, since it was not commited.
    Traceback (most recent call last):
        ...
    RawDBInvalidId: RawDBInvalidId: given id '1' is not in data base.


    Committing a Transaction:

    >>> tm.commit( t1 ) # after you commit
    >>> db_t2[ 1 ]      # it's present
    10


    Aboring a Transaction:

    >>> db_t2[ 1 ] = 20
    >>> db_t2[ 1 ]
    20
    >>> tm.abort( t2 )

    >>> t3    = tm.begin()
    >>> db_t3 = tm.get_shallow_copy( t3 )
    >>> db_t3[ 1 ]
    10


    Operations with an Invalid/Inexistent transaction raises
    L{TransactionManagerInexistent}:

    >>> tm.end( 123 )
    Traceback (most recent call last):
        ...
    TransactionManagerInexistent: TransactionManagerInexistent: given tid '123' is not an existent transaction.

    >>> tm.abort( 456 )
    Traceback (most recent call last):
        ...
    TransactionManagerInexistent: TransactionManagerInexistent: given tid '456' is not an existent transaction.

    >>> tm.get_shallow_copy( 789 )
    Traceback (most recent call last):
        ...
    TransactionManagerInexistent: TransactionManagerInexistent: given tid '789' is not an existent transaction.


    @see: L{rawdb.RawDB}, L{rawdb.ShallowCopy}
    """
    def __init__( self, data=None ):
	self.__last_id = 0

        if data is None:
            data = rawdb.singleton()
	self.set_db( data )
	self.__transactions = {}
    # __init__()


    def set_db( self, db ):
        """Set internal data base.

        @param db: instance of RawDB
        @type  db: L{rawdb.RawDB}
        """
        if not isinstance( db, rawdb.RawDB ):
            raise TypeError( "db must be instance of RawDB" )
        self.__db = db
    # set_db()


    def begin( self ):
        """Begins a transaction.

        @return: transaction identifier.
        @rtype: L{int}
        """
	self.__last_id += 1
	tid = self.__last_id
	self.__transactions[ tid ] = rawdb.ShallowCopy( self.__db )
	return tid
    # begin()


    def is_valid( self, tid ):
        """Check if a transaction identifier is valid.

        @param tid: transaction identifier.
        @type  tid: L{int}

        @return: True if transaction is valid, False otherwise.
        @rtype: L{bool}
        """
        return tid in self.__transactions
    # is_valid()


    def end( self, tid ):
        """End a transaction, commit its changes.

        @precondition: C{tid} L{is_valid}(C{tid}) is True.
        @postcondition: transaction data is put into data base.

        @param tid: transaction identifier.
        @type  tid: L{int}

        @raise TransactionManagerInexistent: if C{tid} is inexistent.

        @see: L{rawdb.ShallowCopy}, L{rawdb.ShallowCopy.commit}
        """
        if not self.is_valid( tid ):
            raise TransactionManagerInexistent( None, tid )

	self.__transactions[ tid ].commit()
        del self.__transactions[ tid ]
    # end()
    commit = end


    def abort( self, tid ):
        """End a transaction, abort its changes.

        @precondition: C{tid} L{is_valid}(C{tid}) is True.
        @postcondition: transaction data is discarded.

        @param tid: transaction identifier.
        @type  tid: L{int}

        @raise TransactionManagerInexistent: if C{tid} is inexistent.
        """
        if not self.is_valid( tid ):
            raise TransactionManagerInexistent( None, tid )

	del self.__transactions[ tid ]
    # abort()


    def get_shallow_copy( self, tid ):
        """Returns the transaction's Shallow (Working) Copy.

        @precondition: C{tid} L{is_valid}(C{tid}) is True.

        @param tid: transaction identifier.
        @type  tid: L{int}

        @raise TransactionManagerInexistent: if C{tid} is inexistent.

        @see: L{rawdb.ShallowCopy}
        """

        if not self.is_valid( tid ):
            raise TransactionManagerInexistent( None, tid )

	return self.__transactions[ tid ]
    # get_shallow_copy()


    def __str__( self ):
        s = []
        for t in self.__transactions.iterkeys():
            s.append( t )

        return  "%s: [%s]" % ( self.__class__.__name__, " ".join( s ) )
    # __str__()
# TransactionManager




class singleton( object ):
    """Class that returns a singleton of TransactionManager.

    @note: This class, when instantiated returns an instance of
           TransactionManager.
    """
    tm = TransactionManager()

    def __new__( self ):
	return self.tm
    # __new__()
# singleton
